package com.meimeixia.pattern.singleton.demo4;

/**
 * 双重检查锁
 * @author: t-zhijiang.zhao
 * @date: 2024/11/26 上午9:23
 */
public class Singleton {
    private Singleton() {
    }

    private static Singleton instance;

    public static Singleton getInstance() {
        // 第一次判断，如果instance的值不为null，那么就不需要抢占锁了，直接返回对象即可
        if (instance == null) {
            synchronized (Singleton.class) {
                // 第二次判断
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    /*可以看到，对于getInstance方法来说，我们是做了两次判断的。
    第一次判断：判断instance是否等于null，若是，则需抢占锁；若不是，则直接返回instance即可，由于并没有去持有锁，所以效率就可以得到提升了
    第二次判断：如果第一次判断时，instance等于null，那么进来判断里面之后我们得先持有一把锁，所以在这儿咱们就编写了一个同步代码块，
    而且锁对象就是当前类的字节码对象，这样，我们就可以在同步代码块里面做第二次判断了。所做判断很简单，无非就是判断instance是否为null，
    若是，则需创建一个本类对象并将其赋值给instance；若不是，则直接返回instance即可
    */



    /*以上双重检查锁方式的实现代码虽然写完了，但你不觉得还存在什么问题吗？双重检查锁方式是一种非常好的单例实现方式，解决了单例、性能、线程安全问题，
    上面的双重检查锁方式看上去完美无缺，其实它是存在问题的，即在多线程的情况下，可能会出现空指针问题，
    出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
    由于以上双重检查锁方式所存在的问题是在多线程的情况下有可能出现，有可能不出现，所以在这儿我就不为大家进行演示了。对了，
    还有引起该问题的原因中涉及到了一个指令重排序操作，在本课程里面我也不会做详细讲解，大家有兴趣的话不妨去看一下Java并发编程。
    既然问题出现了，那如何进行解决呢？要解决双重检查锁方式所带来的空指针异常问题，只需要使用volatile关键字即可，
    因为该关键字的作用就是保证可见性和有序性，在此处体现的就是有序性。
    那么问题又来了，volatile关键字我们应该放在哪呢？记住，我们应该放在instance变量上面，用它来修饰instance变量，这样，就可以保证指令是有序的了。
    */
}
